using Microsoft.Samples.ServiceHosting.StorageClient;

namespace KlearStorage
{
    public abstract class UniversalEntity : TableStorageEntity, System.IEquatable<UniversalEntity>
    {
        public const int ShortFieldMaxLength = 128;
        public const int MediumFieldMaxLength = 512;
        public const int LongFieldMaxLength = 32 * 1024;

        public System.Guid Id { get; set; }

        public override string PartitionKey
        {
            get { return Id.ToString(); }
            set { Id = new System.Guid(value); }
        }
        public override string RowKey
        {
            get { return string.Empty; }
            set { }
        }

        protected UniversalEntity()
        {
            Id = System.Guid.NewGuid();
        }

        public virtual bool Validate(IValidationDictionary ModelState)
        {
            AssertIsNotEmptyGuid(ModelState, () => Id, "Id is required.");
            return ModelState.IsValid;
        }

        protected static void AssertIsNotNullOrBlank(IValidationDictionary ModelState, // TODO: Use AOP (attribute-based)
            System.Linq.Expressions.Expression<System.Func<string>> exp, string errorMessage)
        {
            AssertIsNotNullOrBlank(ModelState, exp, 0, int.MaxValue, errorMessage);
        }

        protected static void AssertIsNotNullOrBlank(IValidationDictionary ModelState, // TODO: Use AOP (attribute-based)
            System.Linq.Expressions.Expression<System.Func<string>> exp, int minLength, int maxLength)
        {
            var memberExpression = (System.Linq.Expressions.MemberExpression)exp.Body;
            string propertyName = memberExpression.Member.Name;

            var errorMessage = "'" + propertyName + "'";
            if (minLength > 0)
                errorMessage += " is required";
            if (minLength > 0 && maxLength < int.MaxValue)
                errorMessage += " and";
            if (maxLength < int.MaxValue)
                errorMessage += " must be shorter than " + maxLength + " characters";
            errorMessage += ".";

            AssertIsNotNullOrBlank(ModelState, exp, minLength, maxLength, errorMessage);
        }

        protected static void AssertIsNotNullOrBlank(IValidationDictionary ModelState, // TODO: Use AOP (attribute-based)
            System.Linq.Expressions.Expression<System.Func<string>> exp, int minLength, int maxLength, string errorMessage)
        {
            var memberExpression = (System.Linq.Expressions.MemberExpression)exp.Body;
            string propertyName = memberExpression.Member.Name;

            var func = exp.Compile();
            var value = func();
            if (value == null || value.Trim().Length < minLength || value.Length >= maxLength) // TODO: Add code to validate agains XSS (script injection) !?!
                ModelState.AddModelError(propertyName, errorMessage);
            else
            {
                value = RemoveScript(value); // TODO: Allow configuration
                var scope = (System.Runtime.CompilerServices.ExecutionScope)func.Target; // TODO: This code makes a lot of assumptions: exp must be () => Value
                var box = (System.Runtime.CompilerServices.IStrongBox)scope.Globals[0];
                var target = box.Value;
                var property = target.GetType().GetProperty(propertyName);
                if (property.CanWrite)
                    property.SetValue(target, value, null);
            }
        }

        protected static void AssertIsNotEmptyGuid<T>(IValidationDictionary ModelState, // TODO: Use AOP (attribute-based)
            System.Linq.Expressions.Expression<System.Func<T>> exp, string errorMessage)
        {
            var memberExpression = (System.Linq.Expressions.MemberExpression)exp.Body;
            string propertyName = memberExpression.Member.Name;

            var value = exp.Compile()();
            if (value.ToString() == System.Guid.Empty.ToString()) // TODO: Add code to validate agains XSS (script injection) !?!
                ModelState.AddModelError(propertyName, errorMessage);
        }

        protected static string RemoveScript(string value)
        {
            return value.Replace("<script", "[script").Replace("</script>", "[/script]"); // TODO: Validate in a more robust way
        }

        public bool Equals(UniversalEntity other)
        {
            return base.Equals(other);
        }
    }

    public abstract class GroupedUniversalEntity : UniversalEntity
    {
        public override string PartitionKey
        {
            get { return Id.ToString(); }
            set { Id = new System.Guid(value); }
        }
        public override string RowKey
        {
            get { return GetGroup(); }
            set { SetGroup(value); }
        }

        public abstract string GetGroup();
        public abstract void SetGroup(string group);

        public override bool Validate(IValidationDictionary ModelState)
        {
            AssertIsNotNullOrBlank(ModelState, () => RowKey, "Group is required.");
            AssertIsNotEmptyGuid(ModelState, () => RowKey, "Group is required.");
            return base.Validate(ModelState);
        }
    }
}